package lx.exalarm.database;

import android.app.Application;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import java.util.ArrayList;
import java.util.List;

import lx.exalarm.alarm.ActionBean;
import lx.exalarm.alarm.AlarmBean;
import lx.exalarm.app.ThisApp;
import lx.exalarm.utils.log.Log;

import static lx.exalarm.database.DBConstants.*;

/**
 * Created by liuxu on 15-2-28.
 *
 * operations about database.
 * actions in alarm are stored as a linked array, only the first action id of the
 * alarm is stored in TABLE_ALARM. the rest can be retrieved one by one.
 */
public class DBManager {

    private static Application sApp;
    private static DBManager sManager;

    private DBHelper mHelper;
    private SQLiteDatabase mDatabase;

    public synchronized static DBManager getInstance() {
        if (sManager == null) {
            sManager = new DBManager();
        }
        return sManager;
    }

    private DBManager() {
        sApp = ThisApp.getInstance();
        mHelper = new DBHelper(sApp);
        mDatabase = mHelper.getWritableDatabase();
    }

    // used to do insert and update
    private ContentValues generateAlarmCV(AlarmBean alarm) {
        ContentValues cv = new ContentValues();
        cv.put(ALARM_HOUR, alarm.getHour());
        cv.put(ALARM_MINUTE, alarm.getMinute());
        cv.put(ALARM_ENABLED, alarm.isEnabled());
        cv.put(ALARM_REPEAT, alarm.getRepeat());

        int firstActionId = -1;
        List<ActionBean> actions = alarm.getActions();
        if (actions != null && actions.size() > 0) {
            firstActionId = actions.get(0).getId();
        }
        cv.put(ALARM_ACTION, firstActionId);
        return cv;
    }

    // used to do insert and update
    private ContentValues generateActionCV(ActionBean action) {
        ContentValues cv = new ContentValues();
        cv.put(ACTION_TYPE, action.getType());
        cv.put(ACTION_INTERVAL, action.getInterval());
        cv.put(ACTION_DURANCE, action.getDurance());
        cv.put(ACTION_DATA, action.getData());
        cv.put(ACTION_ENABLED, action.isEnabled());
        cv.put(ACTION_NEXT, action.getNextId());
        return cv;
    }

    // get action list according to the first action id
    private List<ActionBean> getActionList(int firstActionId) {
        ArrayList<ActionBean> list = new ArrayList<>();
        int id = firstActionId;
        ActionBean action;
        while ((action = getAction(id)) != null) {
            list.add(action);
            id = action.getNextId();
        }
        return list;
    }

    /**
     * get action according to id
     */
    public ActionBean getAction(int actionId) {
        if (actionId < 0) {
            return null;
        }

        Cursor c = mDatabase.rawQuery(
                "SELECT * FROM " + TABLE_ACTION + " where " + ACTION_ID + " = ?",
                new String[] {"" + actionId});

        if (c.moveToFirst()) {
            ActionBean action = new ActionBean();
            action.setId(c.getInt(c.getColumnIndex(ACTION_ID)));
            action.setType(c.getInt(c.getColumnIndex(ACTION_TYPE)));
            action.setInterval(c.getInt(c.getColumnIndex(ACTION_INTERVAL)));
            action.setDurance(c.getInt(c.getColumnIndex(ACTION_DURANCE)));
            action.setData(c.getString(c.getColumnIndex(ACTION_DATA)));
            int enabled = c.getInt(c.getColumnIndex(ACTION_ENABLED));
            action.setEnabled(enabled == 1);
            action.setNextId(c.getInt(c.getColumnIndex(ACTION_NEXT)));
            c.close();
            return action;
        } else {
            return null;
        }
    }

    /**
     * get all alarms
     */
    public List<AlarmBean> getAlarmList() {
        ArrayList<AlarmBean> alarms = new ArrayList<>();

        Cursor c = mDatabase.rawQuery("SELECT * FROM " + TABLE_ALARM, new String[] {});
        while (c.moveToNext()) {
            AlarmBean alarm = new AlarmBean();
            alarm.setId(c.getInt(c.getColumnIndex(ALARM_ID)));
            alarm.setHour(c.getInt(c.getColumnIndex(ALARM_HOUR)));
            alarm.setMinute(c.getInt(c.getColumnIndex(ALARM_MINUTE)));
            alarm.setRepeat(c.getInt(c.getColumnIndex(ALARM_REPEAT)));
            int enabled = c.getInt(c.getColumnIndex(ALARM_ENABLED));
            alarm.setEnabled(enabled == 1);
            int firstActionId = c.getInt(c.getColumnIndex(ALARM_ACTION));
            alarm.setActions(getActionList(firstActionId));
            alarms.add(alarm);
        }
        c.close();

        return alarms;
    }

    /**
     * get alarm according to id
     */
    public AlarmBean getAlarm(int alarmId) {
        if (alarmId < 0) {
            return null;
        }

        Cursor c = mDatabase.rawQuery(
                "SELECT * FROM " + TABLE_ALARM + " where " + ALARM_ID + " = ?",
                new String[] {"" + alarmId});

        if (c.moveToFirst()) {
            AlarmBean alarm = new AlarmBean();
            alarm.setId(c.getInt(c.getColumnIndex(ALARM_ID)));
            alarm.setHour(c.getInt(c.getColumnIndex(ALARM_HOUR)));
            alarm.setMinute(c.getInt(c.getColumnIndex(ALARM_MINUTE)));
            alarm.setRepeat(c.getInt(c.getColumnIndex(ALARM_REPEAT)));
            int enabled = c.getInt(c.getColumnIndex(ALARM_ENABLED));
            alarm.setEnabled(enabled == 1);
            int firstActionId = c.getInt(c.getColumnIndex(ALARM_ACTION));
            alarm.setFirstActionId(firstActionId);
            alarm.setActions(getActionList(firstActionId));
            c.close();
            return alarm;
        } else {
            return null;
        }
    }

    /**
     * add alarm to database, actions contained in the alarm will also be added to database
     */
    public void addAlarm(AlarmBean alarm) {
        if (alarm == null) {
            return;
        }

        List<ActionBean> actions = alarm.getActions();
        mDatabase.beginTransaction();
        try {
            if (actions != null && actions.size() > 0) {
                int actionId = -1;
                for (int i = actions.size() - 1; i >= 0; i --) {
                    ActionBean action = actions.get(i);
                    action.setNextId(actionId);
                    actionId = (int) mDatabase.insert(TABLE_ACTION, null, generateActionCV(action));
                    action.setId(actionId);
                }
            }

            ContentValues alarmCv = generateAlarmCV(alarm);
            int alarmId = (int) mDatabase.insert(TABLE_ALARM, null, alarmCv);
            alarm.setId(alarmId);
            mDatabase.setTransactionSuccessful();
        } finally {
            mDatabase.endTransaction();
        }
    }

    /**
     * delete alarm from database
     */
    public void deleteAlarm(AlarmBean alarm) {
        if (alarm == null) {
            return;
        }

        List<ActionBean> actions = alarm.getActions();
        mDatabase.beginTransaction();
        try {
            mDatabase.delete(TABLE_ALARM,
                    ALARM_ID + " = ?", new String[]{"" + alarm.getId()});
            if (actions != null && actions.size() > 0) {
                for (ActionBean action : actions) {
                    mDatabase.delete(TABLE_ALARM,
                            ACTION_ID + " = ?", new String[] {"" + action.getId()});
                }
            }
            mDatabase.setTransactionSuccessful();
        } finally {
            mDatabase.endTransaction();
        }
    }

    /**
     * update alarm to database
     * @param alarm the alarm
     * @param updateAction
     *          set to true and actions contained in the alarm will be updated as well;
     *          set to false and only the alarm will be updated.
     */
    public void updateAlarm(AlarmBean alarm, boolean updateAction) {
        if (alarm == null) {
            return;
        }

        ContentValues alarmCv = generateAlarmCV(alarm);
        mDatabase.beginTransaction();
        try {
            mDatabase.update(TABLE_ALARM, alarmCv,
                    ALARM_ID + " = ?", new String[] {"" + alarm.getId()});
            if (updateAction) {
                List<ActionBean> actions = alarm.getActions();
                if (actions != null && actions.size() > 0) {
                    for (ActionBean action : actions) {
                        mDatabase.update(TABLE_ACTION, generateActionCV(action),
                                ACTION_ID + " = ?", new String[]{"" + action.getId()});
                    }
                }
            }
            mDatabase.setTransactionSuccessful();
        } finally {
            mDatabase.endTransaction();
        }
    }

    /**
     * update "enabled" field for the alarm. other field will not be updated.
     */
    public void updateAlarmEnabled(AlarmBean alarm) {
        if (alarm == null) {
            return;
        }

        ContentValues cv = new ContentValues();
        cv.put(ALARM_ENABLED, alarm.isEnabled());
        mDatabase.beginTransaction();
        try {
            mDatabase.update(TABLE_ALARM, cv,
                    ALARM_ID + " = ?", new String[] {"" + alarm.getId()});
            mDatabase.setTransactionSuccessful();
        } finally {
            mDatabase.endTransaction();
        }
    }

    /**
     * update action to database
     */
    public void updateAction(ActionBean action) {
        if (action == null) {
            return;
        }

        Log.d("db, updateAction, action=" + action);
        mDatabase.beginTransaction();
        try {
            mDatabase.update(TABLE_ACTION, generateActionCV(action),
                    ACTION_ID + " = ?", new String[]{"" + action.getId()});
            mDatabase.setTransactionSuccessful();
        } finally {
            mDatabase.endTransaction();
        }
    }

    /**
     * add action to database
     */
    public long addAction(ActionBean action) {
        if (action == null) {
            return -1;
        }

        Log.d("db, addAction, action=" + action);
        long id = -1;
        mDatabase.beginTransaction();
        try {
            id = mDatabase.insert(TABLE_ACTION, null, generateActionCV(action));
            mDatabase.setTransactionSuccessful();
        } finally {
            mDatabase.endTransaction();
        }

        return id;
    }
}
